package org.liaohailong.library.http;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.liaohailong.library.http.download.ProgressResponseBody;
import org.liaohailong.library.http.upload.ProgressRequestBody;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okio.BufferedSink;
import okio.Okio;
import okio.Sink;

/**
 * Author: liaohailong
 * Date: 2022/8/12
 * Time: 20:16
 * Description: 封装OkHttp，网络请求
 **/
public class HttpService {
    private static final String TAG = "HttpService";

    private void log(@NonNull String msg, Object... args) {
        String format = String.format(msg, args);
        Log.i(TAG, format);
    }

    private static HttpConfig sConfig = HttpConfig.DEFAULT_TEXT;
    private static OkHttpClient sClient;

    // 缓存请求，统一处理
    private final LinkedList<Call> mCalls = new LinkedList<>();

    public static void init(@NonNull HttpConfig config) {
        sConfig = config;
    }

    private synchronized static OkHttpClient getClient() {
        if (sClient == null) {
            synchronized (HttpService.class) {
                sClient = newClient(sConfig);
            }
        }

        return sClient;
    }

    private static OkHttpClient newClient(@NonNull HttpConfig config) {
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .readTimeout(config.readTimeoutMs, TimeUnit.MILLISECONDS)
                .writeTimeout(config.writeTimeoutMs, TimeUnit.MILLISECONDS)
                .connectTimeout(config.connectTimeoutMs, TimeUnit.MILLISECONDS)
                .retryOnConnectionFailure(config.retryOnConnectionFailure);

        // 网络请求拦截
        for (Interceptor interceptor : config.interceptors) {
            builder.addInterceptor(interceptor);
        }

        // 网络连接中拦截
        for (Interceptor interceptor : config.networkInterceptors) {
            builder.addNetworkInterceptor(interceptor);
        }
        return builder.build();
    }


    public <T> Call get(@NonNull String url,
                        @NonNull Map<String, String> params,
                        @NonNull IHttpCallback<T> callback) {
        return doRequest(url, params, "GET", callback);
    }

    public <T> Call post(@NonNull String url,
                         @NonNull Map<String, String> params,
                         @NonNull IHttpCallback<T> callback) {
        return doRequest(url, params, "POST", callback);
    }

    public <T> Call put(@NonNull String url,
                        @NonNull Map<String, String> params,
                        @NonNull IHttpCallback<T> callback) {
        return doRequest(url, params, "PUT", callback);
    }


    private <T> Call doRequest(@NonNull String url,
                               @NonNull Map<String, String> params,
                               @NonNull String method,
                               @NonNull IHttpCallback<T> callback) {
        Request.Builder builder = new Request.Builder();

        switch (method) {
            case "GET":
                // 拼接参数到url
                url = HttpHelper.appendParams(url, params);
                builder.url(url).get();
                break;
            case "POST":
            case "PUT":
                String json = HttpHelper.sGson.toJson(params);
                MediaType mediaType = MediaType.parse("application/json; charset=utf-8");
                RequestBody body = RequestBody.create(mediaType, json);
                builder.method(method, body);
                break;
            default:
                // 请求不支持
                return null;
        }

        // 追加 header
        for (Map.Entry<String, String> entry : sConfig.headers.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }

        Request request = builder.build();
        Call call = getClient().newCall(request);
        call.enqueue(new SimpleCallback<>(callback));

        saveCall(call);
        return call;
    }


    public <T> Call upload(@NonNull String url,
                           @Nullable Map<String, String> params,
                           @NonNull Map<String, File> files,
                           @NonNull String fileKey,
                           @Nullable HttpConfig config,
                           @NonNull IHttpCallback<T> callback) {
        // 上传文件的网络配置
        if (config == null) {
            config = HttpConfig.DEFAULT_FILE;
        }
        OkHttpClient client = newClient(config);

        Request.Builder builder = new Request.Builder();
        builder.url(url);

        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder();
        // 文本参数
        if (params != null && !params.isEmpty()) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                bodyBuilder.addFormDataPart(name, value);
            }
        }

        // 文件参数
        for (Map.Entry<String, File> entry : files.entrySet()) {
            String name = entry.getKey();
            File file = entry.getValue();
            bodyBuilder.addFormDataPart(fileKey, name, RequestBody.create(null, file));
        }

        MultipartBody body = bodyBuilder.build();
        // 上传进度监听
        ProgressRequestBody progressRequestBody = new ProgressRequestBody(body, callback);
        builder.post(progressRequestBody);

        Request request = builder.build();
        Call call = client.newCall(request);
        call.enqueue(new SimpleCallback<>(callback));

        saveCall(call);
        return call;
    }

    public <T> Call download(@NonNull String url,
                             @NonNull String cacheDir,
                             @NonNull IHttpCallback<T> callback) {
        return download(url, null, cacheDir, null, callback);
    }

    public <T> Call download(@NonNull String url,
                             @Nullable Map<String, String> params,
                             @NonNull String cacheDir,
                             @Nullable HttpConfig config,
                             @NonNull IHttpCallback<T> callback) {
        // 下载文件的网络配置
        if (config == null) {
            config = HttpConfig.DEFAULT_FILE;
        }
        OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(config.connectTimeoutMs, TimeUnit.MILLISECONDS)
                .writeTimeout(config.writeTimeoutMs, TimeUnit.MILLISECONDS)
                .readTimeout(config.readTimeoutMs, TimeUnit.MILLISECONDS)
                .build();

        Request.Builder builder = new Request.Builder();
        url = HttpHelper.appendParams(url, params != null ? params : new HashMap<>());
        builder.url(url).get();

        // 检查是否命中缓存
        String filename = HttpHelper.getFilename(url);
        File file = new File(cacheDir, filename);
        if (file.exists() && file.length() > 0) {
            // 命中缓存
            callback.onDownload(file.getAbsolutePath());
            return null;
        }

        Request request = builder.build();
        Call call = client.newCall(request);
        call.enqueue(new DownloadCallback<>(cacheDir, callback));

        saveCall(call);
        return call;
    }

    private void saveCall(@NonNull Call call) {
        synchronized (mCalls) {
            mCalls.addLast(call);
        }
    }

    private void removeCall(@NonNull Call call) {
        synchronized (mCalls) {
            mCalls.remove(call);
        }
    }


    /**
     * 取消所有未执行的任务
     */
    public void cancel() {
        synchronized (mCalls) {
            for (Call mCall : mCalls) {
                if (mCall.isExecuted() || mCall.isCanceled()) {
                    continue;
                }
                mCall.cancel();
            }
            mCalls.clear();
        }
    }

    private final class SimpleCallback<T> implements Callback {
        @NonNull
        private final IHttpCallback<T> callback;

        public SimpleCallback(@NonNull IHttpCallback<T> callback) {
            this.callback = callback;
        }

        @Override
        public void onFailure(Call call, IOException e) {
            removeCall(call);
            if (call.isCanceled()) {
                Request request = call.request();
                log("cancel on failure: %s", request.toString());
                return;
            }
            HttpHelper.post2main(() -> callback.onFailure(e));
        }

        @Override
        public void onResponse(Call call, Response response) {
            removeCall(call);
            if (call.isCanceled()) {
                Request request = call.request();
                log("cancel on response: %s", request.toString());
                return;
            }
            try {
                ResponseBody body = response.body();
                if (body == null) {
                    String error = "response body is null, response: \n" + response;
                    IllegalStateException exception = new IllegalStateException(error);
                    HttpHelper.post2main(() -> callback.onFailure(exception));
                    return;
                }
                String string = body.string();
                // 主线程回调原始数据
                HttpHelper.post2main(() -> callback.onRawData(string));
            } catch (Exception ex) {
                // 主线程回调错误信息
                HttpHelper.post2main(() -> callback.onFailure(ex));
            }
        }
    }


    private final class DownloadCallback<T> implements Callback {
        @NonNull
        private final String cacheDir;

        @NonNull
        private final IHttpCallback<T> callback;

        public DownloadCallback(@NonNull String cacheDir,
                                @NonNull IHttpCallback<T> callback) {
            this.cacheDir = cacheDir;
            this.callback = callback;
        }

        @Override
        public void onFailure(Call call, IOException e) {
            removeCall(call);
            if (call.isCanceled()) {
                Request request = call.request();
                log("cancel on failure: %s", request.toString());
                return;
            }
            HttpHelper.post2main(() -> {
                callback.onFailure(e);
            });
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            removeCall(call);
            if (call.isCanceled()) {
                Request request = call.request();
                log("cancel on response: %s", request.toString());
                return;
            }

            BufferedSink bufferedSink = null;
            try {
                ResponseBody body = response.body();
                if (body == null) {
                    String error = "response body is null, response: \n" + response;
                    IllegalStateException exception = new IllegalStateException(error);
                    HttpHelper.post2main(() -> callback.onFailure(exception));
                    return;
                }

                // 写入本地
                String url = response.request().url().toString();
                String filename = HttpHelper.getFilename(url);
                HttpHelper.mkdirsIfNotExist(new File(cacheDir));
                File file = new File(cacheDir, filename);

                // 下载文件到本地
                Sink sink = Okio.sink(file);
                bufferedSink = Okio.buffer(sink);
                ProgressResponseBody progressResponseBody = new ProgressResponseBody(body, callback);
                bufferedSink.writeAll(progressResponseBody.source());
                bufferedSink.close();

                HttpHelper.post2main(() -> {
                    callback.onDownload(file.getAbsolutePath());
                });
            } catch (Exception ex) {
                ex.printStackTrace();
                HttpHelper.post2main(() -> callback.onFailure(ex));
            } finally {
                HttpHelper.closeIO(bufferedSink);
            }
        }
    }


}
